// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package render

import (
	"bytes"
	"embed"
	"errors"
	"html/template"
	"io/fs"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

type HTMLOut struct {
	Template    *template.Template
	ViewPath    string   //如果是本地文件路径
	Fs          embed.FS //直接加载嵌入的文件系统
	Delims      Delims
	FuncMap     template.FuncMap //自定义模板函数
	Extension   string           //模板文件后缀,带.
	Mu          sync.RWMutex
	ContentList map[string]string
	IsDebug     bool //是否debug模式，如果是debug模式，则每次加载模板文件
}

// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.
func (r HTMLOut) Instance(layoutName, name string, data any) (Render, error) {

	temp, err := r.loadTemplate(layoutName, name)
	if err != nil {
		panic("加载模板失败:" + err.Error())
	}
	rr := OutHtml{
		Template: temp,
		Name:     name,
		Data:     data,
	}

	return rr, err
}

func (r HTMLOut) AddContentList(name, content string) {
	r.Mu.Lock()
	defer r.Mu.Unlock()
	r.ContentList[name] = content
}

// 读取模板的内容
func (r HTMLOut) GetContentList(name string) (content string, ok bool) {
	if r.IsDebug { //调试模式，直接读取
		r.Mu.Lock()
		defer r.Mu.Unlock()
		content, err := r.readFile(name)
		if err != nil {
			return "", false
		}
		return content, true
	}
	r.Mu.RLock()
	content, ok = r.ContentList[name]
	r.Mu.RUnlock()
	if ok {
		return content, ok
	}
	r.Mu.Lock()
	defer r.Mu.Unlock()
	content, ok = r.ContentList[name]
	if ok {
		return content, ok
	}
	content, err := r.readFile(name)
	if err != nil {
		return "", false
	}
	r.ContentList[name] = content
	return content, true
}

// 解析模板并且输出html
func (r HTMLOut) GetHtml(tempName string, data any) (content template.HTML, err error) {
	if r.FuncMap == nil {
		r.FuncMap = template.FuncMap{}
	}
	tempName = r.normalizeName(tempName)
	contentTpl, ok := r.GetContentList(tempName)
	if !ok {
		err = errors.New(tempName + "布局模板不存在")
		return "", err
	}
	outTpl, err := template.New(tempName).Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).Parse(contentTpl)
	if err != nil {
		return "", err
	}
	if data == nil {
		data = map[string]interface{}{}
	}
	buf := bytes.NewBufferString("")
	err = outTpl.Execute(buf, data)
	contents := buf.String()
	return template.HTML(contents), err
}

func (r HTMLOut) loadTemplate(layoutName, tempName string) (outTpl *template.Template, err error) {

	if r.FuncMap == nil {
		r.FuncMap = template.FuncMap{}
	}
	tempName = r.normalizeName(tempName)
	layoutName = r.normalizeName(layoutName)

	//正式发布版本，不用每次去加载模板文件，直接从map中获取

	if layoutName != "" {
		layout, ok := r.GetContentList(layoutName)
		if !ok {
			err = errors.New(layoutName + "布局模板不存在")
			return
		}
		contentTpl, ok := r.GetContentList(tempName)
		if !ok {
			err = errors.New(tempName + "不存在")
			return
		}

		outTpl = template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap)
		outTpl, err = outTpl.Parse(layout)
		if err != nil {
			return
		}
		outTpl, err = outTpl.Parse(contentTpl)

	} else {
		contentTpl, ok := r.GetContentList(tempName)
		if !ok {
			err = errors.New(tempName + "布局模板不存在")
			return
		}
		outTpl, err = template.New(tempName).Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).Parse(string(contentTpl))
	}

	return
}

// 模板名称统一处理
func (r *HTMLOut) normalizeName(name string) string {
	if name == "" {
		return ""
	}
	// 确保以/开头
	if name[0] != '/' {
		name = "/" + name
	}
	// 去除扩展名
	if strings.HasSuffix(name, r.Extension) {
		name = name[:len(name)-len(r.Extension)]
	}
	return strings.ToLower(filepath.ToSlash(name))
}

// 直接读取本地文件
func (r HTMLOut) readFile(name string) (string, error) {
	name += r.Extension
	var content []byte
	var err error
	if r.ViewPath == "" {
		if name[0] == '/' {
			name = name[1:]
		}
		content, err = r.Fs.ReadFile(name)
	} else {
		content, err = os.ReadFile(r.ViewPath + name)
	}
	if err != nil {
		return "", err
	}
	return string(content), nil
}

// 加载本地文件
func (r HTMLOut) loadFromLocal() (err error) {
	r.Mu.Lock()
	defer r.Mu.Unlock()

	_, err = os.ReadDir(r.ViewPath)
	if err != nil {
		return
	}

	err = filepath.WalkDir(r.ViewPath, func(path string, d os.DirEntry, err error) error {
		if d.IsDir() {
			return nil
		}
		if filepath.Ext(d.Name()) != r.Extension {
			return nil
		}
		content, err := os.ReadFile(path)
		if err != nil {
			return nil
		}
		tempName := path[len(r.ViewPath)+1 : len(path)-len(r.Extension)] //filepath.Base(path)
		tempName = "/" + strings.ToLower(tempName)
		tempName = strings.ReplaceAll(tempName, "\\", "/")
		r.ContentList[tempName] = string(content)
		return nil
	})
	return
}

// 加载fs文件系统模板
func (r HTMLOut) loadFromEmbed() (err error) {
	r.Mu.Lock()
	defer r.Mu.Unlock()
	err = fs.WalkDir(r.Fs, ".", func(path string, d fs.DirEntry, err error) error {
		if path == "." {
			return nil
		}
		if d.IsDir() {
			return nil
		}
		if filepath.Ext(d.Name()) != r.Extension {
			return nil
		}
		content, err := r.Fs.ReadFile(path)
		if err != nil {
			return nil
		}
		tempName := path[0 : len(path)-len(r.Extension)] //filepath.Base(path)
		tempName = "/" + strings.ToLower(tempName)
		tempName = strings.ReplaceAll(tempName, "\\", "/")

		r.ContentList[tempName] = string(content)
		return nil
	})
	return err
}

// 提高效率，在设置模板的时候，自动加载模板文件到内存中，提高效率
func (r HTMLOut) AutoLoadTemplate() {
	if r.ViewPath == "" {
		r.loadFromEmbed()
	} else {
		r.loadFromLocal()
	}
}

type OutHtml struct {
	Template *template.Template
	Name     string
	Data     any
}

func (r OutHtml) Render(w http.ResponseWriter) error {
	r.WriteContentType(w)

	return r.Template.Execute(w, r.Data)
}

// WriteContentType (HTML) writes HTML ContentType.
func (r OutHtml) WriteContentType(w http.ResponseWriter) {
	writeContentType(w, htmlContentType)
}
